using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace SDILReader
{

    public class MethodBodyReader
    {
        public List<ILInstruction> instructions = null;

        protected byte[] il = null;

        private MethodInfo mi = null;

        private int ReadInt16(byte[] _il, ref int position)
        {
            return il[position++] | (il[position++] << 8);
        }

        private ushort ReadUInt16(byte[] _il, ref int position)
        {
            return (ushort)(il[position++] | (il[position++] << 8));
        }

        private int ReadInt32(byte[] _il, ref int position)
        {
            return il[position++] | (il[position++] << 8) | (il[position++] << 16) | (il[position++] << 24);
        }

        private ulong ReadInt64(byte[] _il, ref int position)
        {
            return (ulong)(il[position++] | (il[position++] << 8) | (il[position++] << 16) | (il[position++] << 24) | il[position++] | (il[position++] << 8) | (il[position++] << 16) | (il[position++] << 24));
        }

        private double ReadDouble(byte[] _il, ref int position)
        {
            return il[position++] | (il[position++] << 8) | (il[position++] << 16) | (il[position++] << 24) | il[position++] | (il[position++] << 8) | (il[position++] << 16) | (il[position++] << 24);
        }

        private sbyte ReadSByte(byte[] _il, ref int position)
        {
            return (sbyte)il[position++];
        }

        private byte ReadByte(byte[] _il, ref int position)
        {
            return il[position++];
        }

        private float ReadSingle(byte[] _il, ref int position)
        {
            return il[position++] | (il[position++] << 8) | (il[position++] << 16) | (il[position++] << 24);
        }

        private void ConstructInstructions(Module module)
        {
            byte[] array = il;
            int position = 0;
            instructions = new List<ILInstruction>();
            while (position < array.Length)
            {
                ILInstruction iLInstruction = new ILInstruction();
                OpCode nop = OpCodes.Nop;
                ushort num = array[position++];
                if (num != 254)
                {
                    nop = Globals.singleByteOpCodes[num];
                }
                else
                {
                    num = array[position++];
                    nop = Globals.multiByteOpCodes[num];
                    num = (ushort)(num | 0xFE00u);
                }
                iLInstruction.Code = nop;
                iLInstruction.Offset = position - 1;
                int num2 = 0;
                switch (nop.OperandType)
                {
                    case OperandType.InlineBrTarget:
                        num2 = ReadInt32(array, ref position);
                        num2 += position;
                        iLInstruction.Operand = num2;
                        break;
                    case OperandType.InlineField:
                        num2 = ReadInt32(array, ref position);
                        iLInstruction.Operand = module.ResolveField(num2, mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                        break;
                    case OperandType.InlineMethod:
                        num2 = ReadInt32(array, ref position);
                        try
                        {
                            iLInstruction.Operand = module.ResolveMethod(num2, mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                        }
                        catch
                        {
                            iLInstruction.Operand = module.ResolveMember(num2, mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                        }
                        break;
                    case OperandType.InlineSig:
                        num2 = ReadInt32(array, ref position);
                        iLInstruction.Operand = module.ResolveSignature(num2);
                        break;
                    case OperandType.InlineTok:
                        num2 = ReadInt32(array, ref position);
                        try
                        {
                            iLInstruction.Operand = module.ResolveType(num2);
                        }
                        catch 
                        {
                        }
                        break;
                    case OperandType.InlineType:
                        num2 = ReadInt32(array, ref position);
                        iLInstruction.Operand = module.ResolveType(num2, mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                        break;
                    case OperandType.InlineI:
                        iLInstruction.Operand = ReadInt32(array, ref position);
                        break;
                    case OperandType.InlineI8:
                        iLInstruction.Operand = ReadInt64(array, ref position);
                        break;
                    case OperandType.InlineNone:
                        iLInstruction.Operand = null;
                        break;
                    case OperandType.InlineR:
                        iLInstruction.Operand = ReadDouble(array, ref position);
                        break;
                    case OperandType.InlineString:
                        num2 = ReadInt32(array, ref position);
                        iLInstruction.Operand = module.ResolveString(num2);
                        break;
                    case OperandType.InlineSwitch:
                        {
                            int num3 = ReadInt32(array, ref position);
                            int[] array2 = new int[num3];
                            for (int i = 0; i < num3; i++)
                            {
                                array2[i] = ReadInt32(array, ref position);
                            }
                            int[] array3 = new int[num3];
                            for (int i = 0; i < num3; i++)
                            {
                                array3[i] = position + array2[i];
                            }
                            break;
                        }
                    case OperandType.InlineVar:
                        iLInstruction.Operand = ReadUInt16(array, ref position);
                        break;
                    case OperandType.ShortInlineBrTarget:
                        iLInstruction.Operand = ReadSByte(array, ref position) + position;
                        break;
                    case OperandType.ShortInlineI:
                        iLInstruction.Operand = ReadSByte(array, ref position);
                        break;
                    case OperandType.ShortInlineR:
                        iLInstruction.Operand = ReadSingle(array, ref position);
                        break;
                    case OperandType.ShortInlineVar:
                        iLInstruction.Operand = ReadByte(array, ref position);
                        break;
                    default:
                        throw new Exception("Unknown operand type.");
                }
                instructions.Add(iLInstruction);
            }
        }

        public object GetRefferencedOperand(Module module, int metadataToken)
        {
            AssemblyName[] referencedAssemblies = module.Assembly.GetReferencedAssemblies();
            for (int i = 0; i < referencedAssemblies.Length; i++)
            {
                Module[] modules = Assembly.Load(referencedAssemblies[i]).GetModules();
                for (int j = 0; j < modules.Length; j++)
                {
                    try
                    {
                        return modules[j].ResolveType(metadataToken);
                    }
                    catch
                    {
                    }
                }
            }
            return null;
        }

        public string GetBodyCode()
        {
            string text = "";
            if (instructions != null)
            {
                for (int i = 0; i < instructions.Count; i++)
                {
                    text = text + instructions[i].GetCode() + "\n";
                }
            }
            return text;
        }

        public MethodBodyReader(MethodInfo mi)
        {
            this.mi = mi;
            if (mi.GetMethodBody() != null)
            {
                il = mi.GetMethodBody().GetILAsByteArray();
                ConstructInstructions(mi.Module);
            }
        }
    }

}